#include "BridgedDevice.h"

#include <lib/support/ZclString.h>
#include <app-common/zap-generated/ids/Attributes.h>
#include <app-common/zap-generated/ids/Clusters.h>
#include <app/reporting/reporting.h>
#include <platform/CHIPDeviceLayer.h>
#include <app/util/attribute-storage.h>


#include "Tools.h"

using namespace chip::app::Clusters;

namespace{
    constexpr uint8_t kDeviceVersionDefault = 1;
    constexpr uint16_t kBridgedDeviceBasicClusterRevision = 2u;
    constexpr uint32_t kBridgedDeviceBasicFeatureMap = 0u;

    constexpr int kDescriptorAttributeArraySize = 254;
    constexpr int kNodeLabelSize = 32;

    // Declare Descriptor cluster attributes
    constexpr  DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(descriptorAttrs)
        DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::DeviceTypeList::Id, ARRAY, kDescriptorAttributeArraySize, 0),
        DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::ServerList::Id, ARRAY, kDescriptorAttributeArraySize, 0),
        DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::ClientList::Id, ARRAY, kDescriptorAttributeArraySize, 0),
        DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::PartsList::Id, ARRAY, kDescriptorAttributeArraySize, 0),
    DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();

    // Declare Bridged Device Basic information cluster attributes
    constexpr DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(bridgedDeviceBasicAttrs)
        DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::NodeLabel::Id, CHAR_STRING, kNodeLabelSize, 0), /* NodeLabel */
        DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::Reachable::Id, BOOLEAN, 1, 0), /* Reachable */
        DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::FeatureMap::Id, BITMAP32, 4, 0), /* feature map */
        DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::VendorName::Id, CHAR_STRING, kNodeLabelSize, 0), /* VendorName */
        DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::ProductName::Id, CHAR_STRING, kNodeLabelSize, 0), /* ProductName */
        DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::HardwareVersionString::Id, CHAR_STRING, kNodeLabelSize, 0),
        DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::SoftwareVersionString::Id, CHAR_STRING, kNodeLabelSize, 0),
        DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::ManufacturingDate::Id, CHAR_STRING, kNodeLabelSize, 0),
        DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::ProductURL::Id, CHAR_STRING, kNodeLabelSize, 0),
        DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::ProductLabel::Id, CHAR_STRING, kNodeLabelSize, 0),
        DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::SerialNumber::Id, CHAR_STRING, kNodeLabelSize, 0),
        DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::UniqueID::Id, CHAR_STRING, kNodeLabelSize, 0),
    DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();

    constexpr EmberAfCluster descriptorCluster = DECLARE_DYNAMIC_CLUSTER(Descriptor::Id, descriptorAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr);
    constexpr EmberAfCluster bridgedDeviceBasicCluster = DECLARE_DYNAMIC_CLUSTER(BridgedDeviceBasicInformation::Id, bridgedDeviceBasicAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr);
}


BridgedDevice::BridgedDevice(const ::BasicInformation *node)
    : mInfo(node)
{
    ChipLogProgress(DeviceLayer, "BridgedDevice");
    mEndpointId = node->GetEndpoint();
    mName = node->GetLabel();
    mClusters.push_back(descriptorCluster);
    mParentEndpointId = node->GetParentId();

    if (node->GetDeviceType() != kDeviceTypeBridgedNode) {
        const EmberAfDeviceType rootType = {
            node->GetDeviceType(),
            kDeviceVersionDefault
        };
        mDeviceTypes.push_back(rootType);
    }

    if (node->GetParentId() == kInvalidEndpointId) {
        constexpr EmberAfDeviceType bridgedType = {kDeviceTypeBridgedNode, kDeviceVersionDefault};
        mDeviceTypes.push_back(bridgedType);
        mClusters.push_back(bridgedDeviceBasicCluster);
    }
}

BridgedDevice::~BridgedDevice() {
    if (mEndpointType.cluster != nullptr) {
        delete mEndpointType.cluster;
        mEndpointType.cluster = nullptr;
    }
    if (mClusterDataVersion != nullptr) {
        delete mClusterDataVersion;
        mClusterDataVersion = nullptr;
    }
}

Protocols::InteractionModel::Status BridgedDevice::ReadAttribute(
    ClusterId clusterId,
    AttributeId attributeId,
    uint8_t* buffer, uint16_t maxReadLength)
{
    ChipLogProgress(DeviceLayer, "BasicAttributes: attrId=%d, maxReadLength=%d", attributeId, maxReadLength);
    if (clusterId != BridgedDeviceBasicInformation::Id) {
        return Protocols::InteractionModel::Status::Failure;
    }

    if (attributeId == BridgedDeviceBasicInformation::Attributes::Reachable::Id) {
        tools::GetAttr(buffer, maxReadLength, static_cast<uint8_t>(this->IsReachable() ? 1 : 0));
    }
    else if (attributeId == BridgedDeviceBasicInformation::Attributes::NodeLabel::Id) {
        MutableByteSpan zclNameSpan(buffer, maxReadLength);
        MakeZclCharString(zclNameSpan, mName.c_str());
    }
    else if (attributeId == BridgedDeviceBasicInformation::Attributes::ClusterRevision::Id) {
        tools::GetAttr(buffer, maxReadLength, kBridgedDeviceBasicClusterRevision);
    }
    else if (attributeId == BridgedDeviceBasicInformation::Attributes::FeatureMap::Id) {
        tools::GetAttr(buffer, maxReadLength, kBridgedDeviceBasicFeatureMap);
    }
    else if (attributeId == BridgedDeviceBasicInformation::Attributes::VendorName::Id) {
        MutableByteSpan vendorName(buffer, maxReadLength);
        MakeZclCharString(vendorName, mInfo->GetVendorName().c_str());
    }
    else if (attributeId == BridgedDeviceBasicInformation::Attributes::ProductName::Id) {
        MutableByteSpan productName(buffer, maxReadLength);
        MakeZclCharString(productName, mInfo->GetProductName().c_str());
    }
    else if (attributeId == BridgedDeviceBasicInformation::Attributes::HardwareVersionString::Id) {
        MutableByteSpan hardwareVersion(buffer, maxReadLength);
        MakeZclCharString(hardwareVersion, "v1.2.1");
    }
    else if (attributeId == BridgedDeviceBasicInformation::Attributes::SoftwareVersionString::Id) {
        MutableByteSpan softwareVersion(buffer, maxReadLength);
        MakeZclCharString(softwareVersion, "v2.4.2");
    }
    else if (attributeId == BridgedDeviceBasicInformation::Attributes::ManufacturingDate::Id) {
        MutableByteSpan dateCode(buffer, maxReadLength);
        MakeZclCharString(dateCode, mInfo->GetManufacturingDate().c_str());
    }
    else if (attributeId == BridgedDeviceBasicInformation::Attributes::ProductURL::Id) {
        MutableByteSpan productUrl(buffer, maxReadLength);
        MakeZclCharString(productUrl, "https://leedarson.com/");
    }
    else if (attributeId == BridgedDeviceBasicInformation::Attributes::ProductLabel::Id) {
        MutableByteSpan productLabel(buffer, maxReadLength);
        MakeZclCharString(productLabel, "ProductLabel");
    }
    else if (attributeId == BridgedDeviceBasicInformation::Attributes::SerialNumber::Id) {
        MutableByteSpan serialNumber(buffer, maxReadLength);
        MakeZclCharString(serialNumber, mInfo->GetSerialNumber().c_str());
    }
    else if (attributeId == BridgedDeviceBasicInformation::Attributes::UniqueID::Id) {
        MutableByteSpan uniqueId(buffer, maxReadLength);
        MakeZclCharString(uniqueId, mInfo->GetUniqueId().c_str());
    } else {
        return Protocols::InteractionModel::Status::UnsupportedAttribute;
    }
    return Protocols::InteractionModel::Status::Success;
}

Protocols::InteractionModel::Status BridgedDevice::WriteAttribute(
    ClusterId clusterId,
    const EmberAfAttributeMetadata* attributeMetadata,
    uint8_t* buffer) {
    return Protocols::InteractionModel::Status::UnsupportedRead;
}

void BridgedDevice::SetReachable(bool aReachable) {
    bool changed = (mReachable != aReachable);
    mReachable = aReachable;

    if (aReachable) {
        ChipLogProgress(DeviceLayer, "Device[%s]: Online", mInfo->GetLabel().c_str());
    } else {
        ChipLogProgress(DeviceLayer, "Device[%s]: Offline", mInfo->GetLabel().c_str());
    }

    if (changed) {
        NotifyStatusChanged(BridgedDeviceBasicInformation::Id, BridgedDeviceBasicInformation::Attributes::Reachable::Id);
    }
}

ChipError BridgedDevice::OnReport(const DataBlock * block) {
    if (block->path.clusterId != BridgedDeviceBasicInformation::Id) {
        return CHIP_NO_ERROR;
    }
    SetReachable(block->data.asBool());
    return CHIP_NO_ERROR;
}

void BridgedDevice::SetDelegate(BridgedDeviceDelegate* instance) {
    mDelegate = instance;
}

ChipError BridgedDevice::OnReadAttributes() {
    return CHIP_NO_ERROR;
}

void BridgedDevice::GetRemoteAttribute(ClusterId clusterId, AttributeId attributeId) const {
    if (mDelegate != nullptr) {
        Path path(clusterId, attributeId);
        path.endpointId = mEndpointId;
        mDelegate->OnGetRemoteAttribute(&path);
    }
}

void BridgedDevice::NotifyStatusChanged(ClusterId cluster, AttributeId attribute) {
    auto* path = Platform::New<app::ConcreteAttributePath>(mEndpointId, cluster, attribute);
    if (mDelegate != nullptr) {
        mDelegate->OnStatusChanged(path);
    }
}

void BridgedDevice::SendMessage(DataBlock* message) {
    message->path.endpointId = GetEndpoint();

    if (mDelegate != nullptr) {
        mDelegate->OnMessageSend(message);
    }
}

ChipError BridgedDevice::AttachToIndex(uint16_t index) {
    mEndpointType.clusterCount = static_cast<uint8_t>(mClusters.size());
    mEndpointType.cluster = mClusters.data();
    mClusterDataVersion = new DataVersion[mEndpointType.clusterCount];

    DeviceLayer::StackLock lock;
    const ChipError ret = emberAfSetDynamicEndpoint(index, mEndpointId,
                                   &mEndpointType,
                                   Span<DataVersion>(mClusterDataVersion, mEndpointType.clusterCount),
                                   Span<const EmberAfDeviceType>(mDeviceTypes.data(), mDeviceTypes.size()),
                                   mParentEndpointId);
    ChipLogProgress(DeviceLayer, "Added device %s to dynamic endpoint %d -> %d (index=%d) ret=%s",
            mName.c_str(), mEndpointId, mParentEndpointId, index, ret.AsString());

//    if (mInfo->GetParentId() == kInvalidEndpointId) {
//        chip::app::SetFlatCompositionForEndpoint(mEndpointId);
//    }
    return ret;
}